package com.googlecode.lohr.tools.language;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import lohr.lang.dom.AccessModifier;
import lohr.lang.dom.Arguments;
import lohr.lang.dom.BooleanLiteral;
import lohr.lang.dom.ClosureBody;
import lohr.lang.dom.ClosureLiteral;
import lohr.lang.dom.ClosureParameterDeclaration;
import lohr.lang.dom.CompilationUnit;
import lohr.lang.dom.Expression;
import lohr.lang.dom.IdentifierName;
import lohr.lang.dom.Import;
import lohr.lang.dom.Imports;
import lohr.lang.dom.LohrElement;
import lohr.lang.dom.MethodInvocation;
import lohr.lang.dom.NaturalLiteral;
import lohr.lang.dom.PackageName;
import lohr.lang.dom.ReferenceExpression;
import lohr.lang.dom.SlotDefinition;
import lohr.lang.dom.StringLiteral;
import lohr.lang.dom.TypeParameterDeclaration;
import lohr.lang.dom.TypeReference;

import com.googlecode.lingwah.AbstractProcessor;
import com.googlecode.lingwah.Match;
import com.googlecode.lingwah.ParseResults;

/**
 * This class takes the AST generated by the lingwah parser 
 * and generates a syntax tree composed of the elements in the 
 * lohr.lang.dom package.
 * This is a much more convenient representation of a lohr program than the lingwah AST. 
 * 
 * Besides building a lohr DOM, the LohrDOMBuilder can map between a DOM element 
 * and its corresponding lingwah Match element.  
 * In this way a LohrDOMBuilder is used to map semantic errors in the DOM back to the 
 * text from which it was built.   
 * 
 * At some point
 * 
 * @author ted.stockwell
 *
 */
public class LohrDOMBuilder extends AbstractProcessor {
	
	static final LohrGrammar grammar= LohrGrammar.DEFINITION;
	
    public static LohrElement process(ParseResults results) {
    	LohrDOMBuilder processor= new LohrDOMBuilder();
        results.getLongestMatch().accept(processor);
        return processor.getResult(results.getLongestMatch());
    }	
    
	//	ReferenceExpression.define(sseq(
	//			opt(sseq(choice(ReferenceExpression, MethodInvocation), DOT)), 
	//			IdentifierName.label(ELEMENT_NAME), 
	//			opt(TypeParameterDeclaration)));
	public void completeReferenceExpression(Match expr) {
		IdentifierName identifierName= getResult(expr.findMatchByType(grammar.ParameterizedIdentifier));
		LohrElement target= getResult(expr.findMatchByType(grammar.ReferenceExpression));
		if (target == null)
			target= getResult(expr.findMatchByType(grammar.MethodInvocation));
		putResult(new ReferenceExpression(target, identifierName));
	}
    
    
	public void completeClosureLiteral(Match closureLiteral) {
		ClosureParameterDeclaration parameterDeclaration= getResult(
				closureLiteral.findMatchByType(grammar.ClosureParameterDeclaration));
		if (parameterDeclaration == null)
			parameterDeclaration= new ClosureParameterDeclaration(new ArrayList<TypeReference>());
		
		ClosureBody closureBody= getResult(
				closureLiteral.findMatchByType(grammar.ClosureBody));
		
		putResult(new ClosureLiteral(parameterDeclaration, closureBody));
	}
	
	//ArgumentExpression= choice(Expression, NamedExpression);
	public void completeArgumentExpression(Match closureBody) {
		Expression expression= getResult(closureBody.findByType(grammar.Expression));
		if (expression == null)
			expression= getResult(closureBody.findByType(grammar.NamedExpression));
		putResult(expression);
	}
	public void completeExpression(Match match) {
		processChoice(match);
	}

	public void completeNamedExpression(Match closureBody) {
		//NamedExpression= sseq(IdentifierName, COLON, Expression);
		IdentifierName name= getResult(closureBody.findByType(grammar.IdentifierName));
		Expression expression= getResult(closureBody.findByType(grammar.Expression));
		expression.setExpressionName(name.getName());
		putResult(expression);
	}
	
    // sseq(Statement, zeroOrMore(sseq(opt(SemiColon), Statement)), opt(SemiColon));
	public void completeClosureBody(Match closureBody) {
		List<Match> children= closureBody.findAllMatchByType(grammar.Statement);
		List<Expression> statements= new ArrayList<Expression>();
		for (Match s:children) {
			Expression statement= getResult(s);
			statements.add(statement);
		}
		putResult(new ClosureBody(statements));
	}
	public void completeStatement(Match statement) {
        processChoice(statement);
	}
	//choice(BooleanLiteral, NaturalLiteral, StringLiteral, NullLiteral, ArrayLiteral, ListLiteral, MapLiteral, ClosureLiteral);
	public void completeLiteral(Match literal) {
        processChoice(literal);
	}	
	public void completeBooleanLiteral(Match literal) {
		String text= literal.getText();
		Object result= BooleanLiteral.FALSE;
		if (text.toLowerCase().equals("true")) {
			result= BooleanLiteral.TRUE;
		}
        putResult(result);
	}	
	public void completeNaturalLiteral(Match literal) {
		String text= literal.getText();
		BigInteger value= new BigInteger(text);
        putResult(new NaturalLiteral(value));
	}	
	public void completeStringLiteral(Match literal) {
		String text= literal.getText();
		String value= text.substring(1, text.length()-1);
        putResult(new StringLiteral(value));
	}	
	public void completeSlotDefinition(Match match) {
		AccessModifier modifier= getResult(match.findMatchByType(grammar.AccessModifier));
		Expression initExpresion= getResult(match.findMatchByType(grammar.Expression));
		IdentifierName name= getResult(match.findMatchByType(grammar.IdentifierName));
		TypeReference type= getResult(match.findMatchByType(grammar.TypeReference));
        putResult(new SlotDefinition(modifier, name, type, initExpresion));
	}	
	public void completeAccessModifier(Match literal) {
        putResult(new AccessModifier(literal.getText()));
	}	
	
	// sseq(IdentifierName, opt(TypeParameterDeclaration));
	public void completeParameterizedIdentifier(Match statement) {
		TypeParameterDeclaration parameterDeclaration= getResult(
				statement.findMatchByType(grammar.TypeParameterDeclaration));
		IdentifierName identifierName= getResult(
				statement.findMatchByType(grammar.IdentifierName));
        putResult(new IdentifierName(identifierName, parameterDeclaration));
	}	
	
	
	//	MethodInvocation.define(sseq(
	//			opt(sseq(ReferenceExpression, DOT)), 
	//			ParameterizedIdentifier, 
	//			LPAREN, zeroOrMore(ArgumentExpression).separatedBy(Comma), RPAREN, 
	//			zeroOrMore(choice(NamedExpression, ClosureLiteral)).separatedBy(choice(Comma, ws))));
	public void completeMethodInvocation(Match statement) {
		ReferenceExpression target= getResult(statement.findMatchByType(grammar.ReferenceExpression));
		IdentifierName name= getResult(statement.findMatchByType(grammar.ParameterizedIdentifier));
		
		List<Expression> args= new ArrayList<Expression>();
		for (Match s:statement.findAllMatchByType(grammar.ArgumentExpression)) {
			Expression expression= getResult(s);
			args.add(expression);
		}
		for (Match s:statement.findAllMatchByType(grammar.NamedExpression)) {
			Expression expression= getResult(s);
			args.add(expression);
		}
		for (Match s:statement.findAllMatchByType(grammar.ClosureLiteral)) {
			Expression expression= getResult(s);
			args.add(expression);
		}
		
		putResult(new MethodInvocation(target, name, new Arguments(args)));
	}
	
	//TypeReference.define(oneOrMore(ParameterizedIdentifier).separatedBy(Dot));
	public void completeTypeReference(Match typeReference) {
		List<Match> children= typeReference.findAllMatchByType(grammar.ParameterizedIdentifier);
		List<IdentifierName> nodes= new ArrayList<IdentifierName>();
		for (Match id:children) {
			IdentifierName identifierName= getResult(id);
			nodes.add(identifierName);
		}
		
		putResult(new TypeReference(nodes, (String)null/*name is always null here*/));
	}
	
	// sseq(opt(sseq(IdentifierName, COLON)), TypeReference);
	public void completeNamedTypeReference(Match typeReference) {
		TypeReference reference= getResult(typeReference.findMatchByType(grammar.TypeReference));
		IdentifierName name= getResult(typeReference.findMatchByType(grammar.IdentifierName));
		
		putResult(new TypeReference(reference, name));
	}
	
	// sseq(LT, oneOrMore(NamedTypeReference).separatedBy(Comma), GT);
	public void completeTypeParameterDeclaration(Match typeParameterDeclaration) {
		List<Match> children= typeParameterDeclaration.findAllMatchByType(grammar.NamedTypeReference);
		List<TypeReference> nodes= new ArrayList<TypeReference>();
		for (Match id:children) {
			TypeReference typeReference= getResult(id);
			nodes.add(typeReference);
		}
		
		putResult(new TypeParameterDeclaration(nodes));
	}
	public void completeClosureParameterDeclaration(Match typeParameterDeclaration) {
		List<Match> children= typeParameterDeclaration.findAllMatchByType(grammar.NamedTypeReference);
		List<TypeReference> nodes= new ArrayList<TypeReference>();
		for (Match id:children) {
			TypeReference typeReference= getResult(id);
			nodes.add(typeReference);
		}
		
		putResult(new ClosureParameterDeclaration(nodes));
	}
	
	public void completeIdentifierName(Match identifierName) {
		putResult(new IdentifierName(identifierName.getText()));
	}
	
	// given a choice match, finds the first result for any of the choices. 
	protected void processChoice(Match match) {
		for (Match child:match.getChildren()) {
			Object result= getResult(child);
			if (result != null) {
				putResult(match, result);
				break;
			}
		}
		
	}
	
	public void completePackageName(Match match) {
		List<Match> children= match.findAllMatchByType(grammar.IdentifierName);
		List<IdentifierName> names= new ArrayList<IdentifierName>(children.size());
		for (Match c:children) {
			names.add((IdentifierName)getResult(c));
		}
		putResult(new PackageName(names));
	}
	
	public void completePackageDeclaration(Match packageDeclaration) {
		PackageName packageName= getResult(packageDeclaration.findMatchByType(grammar.PackageName));
		putResult(packageName);
	}
	
	public void completeImportDeclaration(Match packageDeclaration) {
		String protocol= null;
		Match importProtocol= packageDeclaration.findMatchByType(grammar.ImportProtocol);
		if (importProtocol != null) {
			IdentifierName identifierName= getResult(importProtocol.findMatchByType(grammar.IdentifierName));
			if (identifierName != null) {
				protocol= identifierName.getName();
			}
		}
		PackageName packageName= getResult(packageDeclaration.findMatchByType(grammar.PackageName));
		putResult(new Import(packageName, protocol));
	}
	
	public void completeImports(Match match) {
		List<Match> children= match.findAllMatchByType(grammar.ImportDeclaration);
		List<Import> names= new ArrayList<Import>(children.size());
		for (Match c:children) {
			names.add((Import)getResult(c));
		}
		putResult(new Imports(names));
	}
	
	public void completeCompilationUnit(Match compilationUnit) {
		PackageName packageName= getResult(compilationUnit.findMatchByType(grammar.PackageDeclaration));
		Imports imports= getResult(compilationUnit.findMatchByType(grammar.Imports));
		ClosureBody body= getResult(compilationUnit.findMatchByType(grammar.ClosureBody));
		putResult(new CompilationUnit(packageName, imports, body));
	}
//	
//	public void completeExpression(Match expression) {
//		putResult(expression.getFirstChild());
//	}
//	public void completeMethodInvocation(Match expression) {
//		List<Match> matchChildren= expression.getChildren();
//		//sseq(opt(sseq(Expression, DOT)), MethodName, Arguments, opt(Closure));
//		MethodInvocation invocation= new MethodInvocation();
//
//		Expression target= (Expression)getResult(matchChildren.get(0));
//		if (target != null)
//			invocation.setTargetExpression(target);
//		
//		invocation.setMethodName((NameBinding)getResult(matchChildren.get(1)));
//		invocation.setArgumentsList((Arguments)getResult(matchChildren.get(2)));
//
//		ClosureLiteral body= (ClosureLiteral)getResult(matchChildren.get(3));
//		if (body != null)
//			invocation.setClosureDefinition(body);
//	}
	
}
